trashpandafandomcom-20200213-history
Introduction to Python's slots
Here we are going to look into the easy way to speed up your Python code with __slots__ and its technical implementation details. This page is laregly all notes from the medium article "A quick dive into Python's "__slots__" " by Stephen Jayakar . What is it? __slots__ is an attribute you can add to a Python class when defining it. You define slots with the possible attributes that an instance of an object can possess. It is a special attribute that allows you to explicitly state which instance attributes you expect your object instances to have. Here is an example of how you use it : class WithSlots: __slots__ = ('x', 'y') def __init__(self, x, y): self.x, self.y = x, y For instances of this class, you can use self.x and self.y in the same ways as a normal class instance. However, one key difference between this and instancing from a normal class is that you cannot add or remove attributes from this class's instances. Say the instance was called w : you couldn't write w.z = 2 without causing an error. Why use it? The biggest higher-level reasons to use __slots__ are : #'Faster attribute getting and setting' due to data structure optimization. #'Reduced memory usage' for class instances. The memory saving comes from storing value references in slots instead of __dict__ , and denying __dict__ and __weakfref__ creation if parent classes deny them and you declare __slots__ . Why you shouldn't use it You wouldn't want to use it if your class has attributes that change during run-time (dynamic attributes) or if there's a complicated object inheritance tree. Examples Lets look at some examples of when __slots__ is faster, starting with mass instanciation. Using Python's "timeit" module and this code snippet, we get the following : class WithoutSlots: def __init___(self, x, y, z): self.x = x self.y = y self.z = z class WithSlots: __slots__ = ('x', 'y', 'z') def __init__(self, x, y, z): self.x = x self.y = y self.z = z def instance_fn(cls): def instance(): x = cls(1, 2, 3) return instance Without Slots: 0.3909880230203271 With Slots: 0.31494391383603215 (averaged over 100000 iterations) In general, instantiation time is not really improved by using __slots__ . Despite not having to create __dict__ , there's other overhead that needs to be done with slots that we'll go into later, which results in a similar runtime to copying over the dictionary from the actual class. The real speedup comes into play when we start getting and setting values in rapid succession : def get_set_fn(cls): x = cls(list(range(26))) def get_set(): x.y = x.z + 1 x.a = x.b - 1 x.d = x.q + 3 x.i = x.j - 1 x.z = x.y / 2 return get_set Without Slots: 11.59717286285013 With Slots: 9.243316248897463 (averaged over 100000 iterations) Thats a huge speed increase! Here is another quick example of using and not using slots : # Without Slots : class WithoutSlots(object): def __init__(self, name, identifier): self.name = name self.identifier = identifier # With Slots : class WithSlots(object): __slots__ = 'identifier' def __init__(self, name, identifier): self.name = name self.identifier = identifier x = WithSlots("My Name", "My Identifier") print(x.name) print(x.identifier) My Name My Identifier in 0.1s Now say if we wrote x.thing = "New Thing" , we would get the error : AttributeError: 'WithSlots' object has no attribute 'thing' However if we called the same thing but using the WithoutSlots class, we would not get this error : x = WithoutSlots("My Name", "My Identifier") x.thing = "New Thing" No attribute error produced here. Conslusion In Python, every class can have instance attributes, By default Python uses dict to store an object's instance attributes. This is really helpful as it allows setting new attributes at run-time. However, for small classes with known attributes it might be a bottleneck. The dict wastes a lot of RAM. Python can't allocate a static amount of memory at object creation to store all the attributes. Therefore it sucks a lot of RAM if you create a lof of objects (this would need to be A LOT). Still there is a way to get around this issue, it involves using __slots__ to tell Python not to use a dict, and only allocate space for a fixed set of attributes. References A quick dive into Python's "__slots__" by Stephen Jayakar . Usage of __slots__? - Stackoverflow __slots__ Magic - Python Tips